#pragma once

#include <stdexcept>
#include "utility.hpp"

namespace zuck
{
namespace __detail
{
	template<::zuck::size_t _T_size>
	[[nodiscard]] constexpr ::zuck::size_t _Get_bytes(const ::zuck::size_t __c)
	{
		if constexpr (_T_size > 0) {
			constexpr ::zuck::size_t TMax = static_cast<::zuck::size_t>(-1) / _T_size;
			if (__c > TMax) {
				throw ::std::bad_array_new_length{};
			}
		}
		return _T_size * __c;
	}

	template<typename T>
	[[nodiscard]] inline T* _Allocate(const ::zuck::size_t bytes)
	{
		if (!bytes) {
			return null;
		}
		return static_cast<T*>(
			::operator new(bytes, ::std::align_val_t{ ::zuck::new_align_v<T> }));
	}

	template<typename T>
	inline void _Deallocate(T* ptr, const size_t bytes)
	{
		if (!ptr || !bytes) {
			return;
		}
		::operator delete(ptr, bytes, ::std::align_val_t{ ::zuck::new_align_v<T> });
	}
}

	template<typename T>
	class allocator {
	public:
		using value_type	  = T;
		using pointer		  = T*;
		using const_pointer   = const T*;
        using reference       = T&;
		using const_reference = const T&;
		using size_type		  = ::zuck::size_t;
		using difference_type = ::zuck::ptrdiff_t;
	public:
		constexpr explicit allocator() = default;

		constexpr allocator(const allocator&) = default;

		constexpr allocator(allocator&&) = default;

		~allocator()noexcept {}

	public:
		[[nodiscard]] ZCONSTEXPR T* allocate(const size_type __count)const
		{
			return __detail::_Allocate<T>(
				__detail::_Get_bytes<sizeof(T)>(__count));
		}
		
		ZCONSTEXPR void deallocate(T* __data, const size_type __count)const
		{
			__detail::_Deallocate<T>(
				__data, __detail::_Get_bytes<sizeof(T)>(__count));
		}

	public:
		allocator& operator=(const allocator&) = default;

		allocator& operator=(allocator&&) = default;
    };


	template<typename Alloc>
	class allocator_traits {
	private:
		using _Tp = typename Alloc::value_type;
	public:
		using allocator_type = Alloc;

		using value_type      = typename Alloc::value_type;
		using pointer         = typename Alloc::pointer;
		using const_pointer   = typename Alloc::const_pointer;
		using reference       = typename Alloc::reference;
		using const_reference = typename Alloc::const_reference;

		using size_type = ::zuck::size_t;

		using difference_type = ::zuck::ptrdiff_t;

		template<typename rebind_type>
		using rebind_alloc = ::zuck::allocator<rebind_type>;

		template<typename rebind_type>
		using rebind_traits = allocator_traits<rebind_type>;

	public:
		template<typename ...Args>
		inline static void construct(_Tp* ptr, Args&&... args)
		{
			::new (static_cast<void*>(ptr)) _Tp(::zuck::forward<Args>(args)...);
		}

		template<typename ...Args>
		inline static void construct(_Tp& object, Args&&... args)
		{
			::new (static_cast<void*>(::zuck::address_of(object))) _Tp(::zuck::forward<Args>(args)...);
		}

		static void construct_range(_Tp* begin, _Tp* const end, const _Tp* srcBegin, const _Tp* const srcEnd)
		{
			for (; begin != end && srcBegin != srcEnd; ++begin, ++srcBegin) {
				::new ((void*) begin) _Tp(*srcBegin);
			}
		}

		static void construct_range(_Tp* begin, _Tp* const end, _Tp args)
		{
			for (; begin != end; ++begin) {
				::new ((void*)begin) _Tp(args);
			}
		}

		inline static void destruct(_Tp& object)
		{
			object.~_Tp();
		}

		static void destruct_range(_Tp* begin, _Tp* const end)
		{
			for (;begin != end; ++begin) {
				destruct(*begin);
			}
		}

		[[nodiscard]] static ZCONSTEXPR _Tp* allocate(const Alloc& al, const size_t count)
		{
			return al.allocate(count);
		}

		static ZCONSTEXPR void deallocate(const Alloc& al, _Tp* ptr, const size_t count)
		{
			al.deallocate(ptr, count);
		}

		[[nodiscard]] static constexpr size_type max_size()
		{
			return (max_env_size / sizeof(_Tp)) >> 1;
		}
	};

	template<typename DataType>
	using data_traits = allocator_traits<allocator<DataType>>;
}

